import {App, LinkCache, TAbstractFile, TFile} from 'obsidian';
import { LinksHandler, PathChangeInfo } from './links-handler';
import { Utils } from './utils';
import { path } from './path';

export interface MovedAttachmentResult {
	movedAttachments: PathChangeInfo[]
	renamedFiles: PathChangeInfo[],
}

export class FilesHandler {
	constructor(
		private app: App,
		private lh: LinksHandler,
		private consoleLogPrefix: string = "",
		private ignoreFolders: string[] = [],
		private ignoreFilesRegex: RegExp[] = [],
	) { }

	isPathIgnored(path: string): boolean {
		if (path.startsWith("./"))
			path = path.substring(2);

		for (let folder of this.ignoreFolders) {
			if (path.startsWith(folder)) {
				return true;
			}
		}

		for (let fileRegex of this.ignoreFilesRegex) {
			let testResult = fileRegex.test(path)
			// console.log(path,fileRegex,testResult)
			if(testResult) {
				return true;
			}
		}
	}

	async createFolderForAttachmentFromLink(link: string, owningNotePath: string) {
		let newFullPath = this.lh.getFullPathForLink(link, owningNotePath);
		return await this.createFolderForAttachmentFromPath(newFullPath);
	}

	async createFolderForAttachmentFromPath(filePath: string) {
		let newParentFolder = filePath.substring(0, filePath.lastIndexOf("/"));
		try {
			//todo check filder exist
			await this.app.vault.createFolder(newParentFolder)
		} catch { }
	}

	generateFileCopyName(originalName: string): string {
		let ext = path.extname(originalName);
		let baseName = path.basename(originalName, ext);
		let dir = path.dirname(originalName);
		for (let i = 1; i < 100000; i++) {
			let newName = dir + "/" + baseName + " " + i + ext;
			let existFile = this.lh.getFileByPath(newName);
			if (!existFile)
				return newName;
		}
		return "";
	}

	// getExtension(path: string): string {
	// 	let baseName = path.split(/[\\/]/).pop(), // extracts file name from full path
	// 		// (supports separators `\\` and `/`)
	// 		pos = baseName.lastIndexOf("."); // gets the last position of `.`
	// 	if (baseName === "" || pos < 1) // if the file name is empty or ...
	// 		return ""; // the dot not found (-1) or comes first (0)
	// 	return baseName.slice(pos + 1); // extracts extension ignoring "."
	// }

	// isMarkdownFile(link: string, markdownFiles: TFile[]): boolean {
	// 	let included = false;
	// 	markdownFiles.forEach((markFile: TFile) => {
	// 		if (markFile.basename == link) {
	// 			included = true;
	// 		}
	// 	});
	// 	return included;
	// }

	// 去除掉link中的路径后，是否是vault中已存在的markdown文件判断
	// 这里的逻辑是，如果link是在vault中存在的markdown文件，则不用去移动。
	// link是将要被移动的附件，markdown是loop vault中所有的markdown文件
	isInVaultMarkdownNote(link: LinkCache, markdown: TFile): boolean {
		// const extensionList = ["5", "xyz"]
		const linkFileName = link.link.split("/").slice(-1)[0];
		// 这里linkFileName如果不以.md结尾，则可能是一个附件或者是markdown
	    if (linkFileName.endsWith(".excalidraw")) { // TODO: 将这个配置化，作为忽略的md文件，不认为其是md。
			return false; // 这里将.excalidraw.md作为需要移动的附件，因此直接返回false;
		} else if (linkFileName.endsWith(".md")){ // 如果有.md扩展名，那么直接和markdown的完整名比较 // 如果被移动的是.md，那么这个md如果在vault中存在，那就不被过滤 // 不被过滤意味着需要移动
			return linkFileName === markdown.name;
		} else if (linkFileName.indexOf(".") != -1){ // 如果不是.md扩展名，但是仍然包含.
			return linkFileName + ".md" === markdown.name;
			// const extension = linkFileName.split(".").slice(-1)[0] // 这里要判断extension是否真正的是扩展名
			// if (extension.length > 10 || this.excludedExtensions.indexOf(extension) != -1) { // 扩展名大于10个字符就默认不是扩展名 || // 根据配置，这些不是扩展名
			// 	return linkFileName === markdown.basename;
			// }
			// else {
			// 	return false; // 如果是扩展名，那么肯定就不是markdown了，因为在第一个分支已经判断了
			// }
		} else if (linkFileName.indexOf(".") == -1) { // 如果不包含，那么仅需要用其和markdown的basename比较，全不匹配的就是附件了
			return linkFileName === markdown.basename; // 如果没有.md扩展名，那么和markdown的basename比较
		}
		else { // 不以.md结尾，但又含有.的文件，都不认为是vault内的markdown
			// 有个bug就是markdown basename本身含有. e.g. 测试.net framework3.5，这种应该放入上一档
			return false;
		}
	}
	async moveCachedNoteAttachments(oldNotePath: string, newNotePath: string,
		deleteExistFiles: boolean, attachmentsSubfolder: string): Promise<MovedAttachmentResult> {

		if (this.isPathIgnored(oldNotePath) || this.isPathIgnored(newNotePath))
			return;
		const markdownFiles = this.app.vault.getMarkdownFiles();
		//try to get embeds for old or new path (metadataCache can be updated or not)
		//!!! this can return undefined if note was just updated
		let links = this.app.metadataCache.getCache(newNotePath)?.links;
		if (!links)
			links = this.app.metadataCache.getCache(oldNotePath)?.links;

		// TODO: 再增强就是根据配置文件来filter
		// 这里的双重filter意思是，将link是自己vault中的markdown文件给排除掉，完成（这里做一个增强判断，l.link是否是一个vault内的有效笔记）
		links = links && links.filter((l) => markdownFiles.filter(markdown => this.isInVaultMarkdownNote(l, markdown)).length === 0) || []
		let embeds = this.app.metadataCache.getCache(newNotePath)?.embeds;
		if (!embeds)
			embeds = this.app.metadataCache.getCache(oldNotePath)?.embeds;

		// TODO: 再增强就是根据配置文件来filter
		// 这里的双重filter意思是，将link是自己vault中的markdown文件给排除掉，完成（这里做一个增强判断，l.link是否是一个vault内的有效笔记）
		embeds = embeds && embeds.filter((l) => markdownFiles.filter(markdown => this.isInVaultMarkdownNote(l, markdown)).length === 0) || []
		let linksOrEmbeds = [...links, ...embeds]
		if (!linksOrEmbeds || linksOrEmbeds.length === 0)
			return;

		let result: MovedAttachmentResult = {
			movedAttachments: [],
			renamedFiles: []
		};

		for (let embed of linksOrEmbeds) {
			// let link = embed.link;
			let link = this.lh.splitLinkToPathAndSection(embed.link).link;

			let oldLinkPath = this.lh.getFullPathForLink(link, oldNotePath);

			if (result.movedAttachments.findIndex(x => x.oldPath == oldLinkPath) != -1)
				continue;//already moved

			let file = this.lh.getFileByLink(link, oldNotePath);
			if (!file) {
				file = this.lh.getFileByLink(link, newNotePath);
				if (!file) {
					console.error(this.consoleLogPrefix + oldNotePath + " has bad embed (file does not exist): " + link);
					continue;
				}
			}

			//if attachment not in the note folder, skip it
			// = "." means that note was at root path, so do not skip it
			if (path.dirname(oldNotePath) != "." && !path.dirname(oldLinkPath).startsWith(path.dirname(oldNotePath)))
				continue;

			let newLinkPath = this.lh.getFullPathForLink(link, newNotePath);

			if (attachmentsSubfolder.contains("${filename}")) {
				let oldLinkPathBySetting = this.getNewAttachmentPath(file.path, oldNotePath, attachmentsSubfolder);
				if (oldLinkPath == oldLinkPathBySetting) {
					newLinkPath = this.getNewAttachmentPath(file.path, newNotePath, attachmentsSubfolder);
				}
			}

			if (newLinkPath == file.path)
				continue; //nothing to change
			if (newLinkPath.endsWith(".excalidraw")){
				newLinkPath += ".md"; // excalidraw附件真实文件名为.excalidraw.md，但是这里比较的文件名只有.excalidraw，因此在真正移动的时候需要将.md加回去
			}

			let res = await this.moveAttachment(file, newLinkPath, [oldNotePath, newNotePath], deleteExistFiles);
			result.movedAttachments = result.movedAttachments.concat(res.movedAttachments);
			result.renamedFiles = result.renamedFiles.concat(res.renamedFiles);

		}

		return result;
	}

	getNewAttachmentPath(oldAttachmentPath: string, notePath: string, subfolderName: string): string {
		let resolvedSubFolderName = subfolderName.replace(/\${filename}/g, path.basename(notePath, ".md"));
		let newPath = (resolvedSubFolderName == "") ? path.dirname(notePath) : path.join(path.dirname(notePath), resolvedSubFolderName);
		newPath = Utils.normalizePathForFile(path.join(newPath, path.basename(oldAttachmentPath)));
		return newPath;
	}


	async collectAttachmentsForCachedNote(notePath: string, subfolderName: string,
		deleteExistFiles: boolean): Promise<MovedAttachmentResult> {

		if (this.isPathIgnored(notePath))
			return;

		let result: MovedAttachmentResult = {
			movedAttachments: [],
			renamedFiles: []
		};
		const markdownFiles = this.app.vault.getMarkdownFiles();
		//!!! this can return undefined if note was just updated
		let embeds = this.app.metadataCache.getCache(notePath)?.embeds;
		embeds = embeds && embeds.filter((l) => markdownFiles.filter(markdown => this.isInVaultMarkdownNote(l, markdown)).length === 0) || undefined
		if (embeds) {
			for (let embed of embeds) {
				let link = embed.link;

				let fillPathLink = this.lh.getFullPathForLink(link, notePath);
				if (result.movedAttachments.findIndex(x => x.oldPath == fillPathLink) != -1)
					continue;//already moved

				let file = this.lh.getFileByLink(link, notePath)
				if (!file) {
					console.error(this.consoleLogPrefix + notePath + " has bad embed (file does not exist): " + link);
					continue;
				}



				let newPath = this.getNewAttachmentPath(file.path, notePath, subfolderName);


				if (newPath == file.path)//nothing to move
					continue;

				let res = await this.moveAttachment(file, newPath, [notePath], deleteExistFiles);

				result.movedAttachments = result.movedAttachments.concat(res.movedAttachments);
				result.renamedFiles = result.renamedFiles.concat(res.renamedFiles);
			}
		}

		//!!! this can return undefined if note was just updated
		let links = this.app.metadataCache.getCache(notePath)?.links;
		links = links && links.filter((l) => markdownFiles.filter(markdown => this.isInVaultMarkdownNote(l, markdown)).length === 0) || []
		if (links) {
			for (let l of links) {
				let link = this.lh.splitLinkToPathAndSection(l.link).link;

				if (link.startsWith("#")) //internal section link
					continue;

				if (link.endsWith(".md"))
					continue;

				let fillPathLink = this.lh.getFullPathForLink(link, notePath);
				if (result.movedAttachments.findIndex(x => x.oldPath == fillPathLink) != -1)
					continue;//already moved

				let file = this.lh.getFileByLink(link, notePath)
				if (!file) {
					console.error(this.consoleLogPrefix + notePath + " has bad link (file does not exist): " + link);
					continue;
				}

				let newPath = this.getNewAttachmentPath(file.path, notePath, subfolderName);

				if (newPath == file.path)//nothing to move
					continue;

				let res = await this.moveAttachment(file, newPath, [notePath], deleteExistFiles);

				result.movedAttachments = result.movedAttachments.concat(res.movedAttachments);
				result.renamedFiles = result.renamedFiles.concat(res.renamedFiles);
			}
		}

		return result;
	}


	async moveAttachment(file: TFile, newLinkPath: string, parentNotePaths: string[], deleteExistFiles: boolean): Promise<MovedAttachmentResult> {
		const path = file.path;

		let result: MovedAttachmentResult = {
			movedAttachments: [],
			renamedFiles: []
		};

		if (this.isPathIgnored(path))
			return result;


		if (path == newLinkPath) {
			console.warn(this.consoleLogPrefix + "Can't move file. Source and destination path the same.")
			return result;
		}

		await this.createFolderForAttachmentFromPath(newLinkPath);

		// let linkedNotes = this.lh.getCachedNotesThatHaveLinkToFile(path);
		// if (parentNotePaths) {
		// 	for (let notePath of parentNotePaths) {
		// 		linkedNotes.remove(notePath);
		// 	}
		// }

		if (path !== file.path) {
			console.warn(this.consoleLogPrefix + "File was moved already")
			return await this.moveAttachment(file, newLinkPath, parentNotePaths, deleteExistFiles);
		}

		// //if no other file has link to this file - try to move file
		// //if file already exist at new location - delete or move with new name
		// if (linkedNotes.length == 0) {
		let existFile = this.lh.getFileByPath(newLinkPath);
		if (!existFile) {
			//move
			console.log(this.consoleLogPrefix + "move file [from, to]: \n   " + path + "\n   " + newLinkPath)
			result.movedAttachments.push({ oldPath: path, newPath: newLinkPath })
			await this.app.vault.rename(file, newLinkPath);
		} else {
			if (deleteExistFiles) {
				//delete
				console.log(this.consoleLogPrefix + "delete file: \n   " + path)
				result.movedAttachments.push({ oldPath: path, newPath: newLinkPath })
				await this.app.vault.trash(file, true);
			} else {
				//move with new name
				let newFileCopyName = this.generateFileCopyName(newLinkPath)
				console.log(this.consoleLogPrefix + "copy file with new name [from, to]: \n   " + path + "\n   " + newFileCopyName)
				result.movedAttachments.push({ oldPath: path, newPath: newFileCopyName })
				await this.app.vault.rename(file, newFileCopyName);
				result.renamedFiles.push({ oldPath: newLinkPath, newPath: newFileCopyName })
			}
		}
		// }
		// //if some other file has link to this file - try to copy file
		// //if file already exist at new location - copy file with new name or do nothing
		// else {
		// 	let existFile = this.lh.getFileByPath(newLinkPath);
		// 	if (!existFile) {
		// 		//copy
		// 		console.log(this.consoleLogPrefix + "copy file [from, to]: \n   " + path + "\n   " + newLinkPath)
		// 		result.movedAttachments.push({ oldPath: path, newPath: newLinkPath })
		// 		await this.app.vault.copy(file, newLinkPath);
		// 	} else {
		// 		if (deleteExistFiles) {
		// 			//do nothing
		// 		} else {
		// 			//copy with new name
		// 			let newFileCopyName = this.generateFileCopyName(newLinkPath)
		// 			console.log(this.consoleLogPrefix + "copy file with new name [from, to]: \n   " + path + "\n   " + newFileCopyName)
		// 			result.movedAttachments.push({ oldPath: file.path, newPath: newFileCopyName })
		// 			await this.app.vault.copy(file, newFileCopyName);
		// 			result.renamedFiles.push({ oldPath: newLinkPath, newPath: newFileCopyName })
		// 		}
		// 	}
		// }
		return result;
	}




	async deleteEmptyFolders(dirName: string) {
		if (this.isPathIgnored(dirName))
			return;

		if (dirName.startsWith("./"))
			dirName = dirName.substring(2);


		let list = await this.app.vault.adapter.list(dirName);
		for (let folder of list.folders) {
			await this.deleteEmptyFolders(folder)
		}

		list = await this.app.vault.adapter.list(dirName);
		if (list.files.length == 0 && list.folders.length == 0) {
			console.log(this.consoleLogPrefix + "delete empty folder: \n   " + dirName)
			if (await this.app.vault.adapter.exists(dirName))
				await this.app.vault.adapter.rmdir(dirName, false);
		}
	}

	async deleteUnusedAttachmentsForCachedNote(notePath: string) {
		if (this.isPathIgnored(notePath))
			return;

		//!!! this can return undefined if note was just updated
		let embeds = this.app.metadataCache.getCache(notePath)?.embeds;
		if (embeds) {
			for (let embed of embeds) {
				let link = embed.link;

				let fullPath = this.lh.getFullPathForLink(link, notePath);
				let linkedNotes = this.lh.getCachedNotesThatHaveLinkToFile(fullPath);
				if (linkedNotes.length == 0) {
					let file = this.lh.getFileByLink(link, notePath, false);
					if (file) {
						try {
							await this.app.vault.trash(file, true);
						} catch { }
					}
				}
			}
		}

	}
}


